package com.mrx.spring.example.controller;

import com.mrx.commons.annotations.XLock;
import com.mrx.commons.aop.enums.LockType;
import com.mrx.commons.trace.TraceUtils;
import com.mrx.spring.example.service.TestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author Mr.X
 * @since 2023-11-10 16:15
 **/
@RestController
@RequestMapping("/test")
public class TestController {

    private static final Logger logger = LoggerFactory.getLogger(TestController.class);

    @Resource
    private TestService testService;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @GetMapping("/test")
    public String test() {
        logger.info("traceId: {}", TraceUtils.getTraceId());
        testService.asyncTestTask();
        return "Success";
    }

    @GetMapping("/lockTest")
    @XLock(value = "#id", lockType = LockType.WAIT_LOCK, retryTimes = 1)
    public String lockTest(@RequestParam String id) throws Exception {
        Thread.sleep(3000);
        return "Success: " + id;
    }

    @GetMapping("/transactionTest")
    @Transactional(rollbackFor = Throwable.class)
    public String transactionTest(@RequestParam Integer id) {
        testService.addTest(id);
        testService.afterCommit(id);
        applicationEventPublisher.publishEvent(id);
        return "Success: " + id;
    }

    @GetMapping("/afterCommitTest")
    @Transactional(rollbackFor = Throwable.class)
    public String afterCommitTest(@RequestParam Integer id) {
        testService.addTest(id);
        testService.afterCommit(id);
        return "Success: " + id;
    }

    @GetMapping("/asyncTransaction")
    public String asyncTransaction(@RequestParam Integer id) {
        try {
            testService.asyncTransaction(id).get();
        } catch (Exception e) {
            logger.info("出现异常", e);
            testService.addTest(999);
        }
        return "Success: " + id;
    }

    @GetMapping("/newTransaction")
    @Transactional(rollbackFor = Throwable.class)
    public String newTransaction(@RequestParam Integer id) {
        testService.addTest(id);
        try {
            testService.newTransaction(id);
        } catch (Exception e) {
            // 捕获了嵌套事务的异常将会导致
            // Transaction rolled back because it has been marked as rollback-only
            logger.debug("捕获嵌套事务抛出的异常");
        }
        return "Success: " + id;
    }

    @GetMapping("/newTransaction2")
    @Transactional(rollbackFor = Throwable.class)
    public String newTransaction2(@RequestParam Integer id) {
        testService.addTest(id);
        try {
            testService.newTransaction2(id);
        } catch (Exception e) {
            logger.debug("捕获嵌套事务抛出的异常");
        }
        return "Success: " + id;
    }

    @GetMapping("/manualParallelTransaction")
    @Transactional(rollbackFor = Throwable.class)
    public String manualParallelTransaction(@RequestParam Integer id, SimpleAsyncTaskExecutor executor) {
        // 这样应该是有问题的, 不过因为用的 sqlite 会导致拿不到数据库锁而报错
        try {
            CompletableFuture.runAsync(() -> {
                testService.addTest(id);
                testService.addTest(id);
                testService.addTest(id);
                throw new RuntimeException("测试异常回滚");
            }, executor).get();
        } catch (Exception e) {
            logger.debug("捕获异步任务异常", e);
        }
        testService.addTest(0);
        return "Success: " + id;
    }

    @GetMapping("/sseTest")
    public SseEmitter sseTest() throws Exception {
        SseEmitter emitter = new SseEmitter();
        for (int i = 0; i < 10; i++) {
            emitter.send("Hello " + i);
            TimeUnit.MILLISECONDS.sleep(500);
        }
        emitter.complete();
        return emitter;
    }

}
